package message

import (
	"bytes"
	"crypto/ecdsa"
	"encoding/hex"
	"fmt"
	"github.com/wanchain/go-mpc/common"
	"github.com/wanchain/go-mpc/common/hexutil"
	"github.com/wanchain/go-mpc/crypto"
	"github.com/wanchain/go-mpc/crypto/sha3"
	"github.com/wanchain/go-mpc/log"
	"github.com/wanchain/go-mpc/p2p/discover"
	"github.com/wanchain/go-mpc/storeman/osmconf"
	"github.com/wanchain/go-mpc/storeman/schnorrcomm"
	"github.com/wanchain/go-mpc/storeman/storemanmpc/protocol"
	"math/big"
	"sync"
)

type MpcMessage struct {
	// PeerId = nil : broadcast && plain text
	// PeerId != nil : p2p message && encrypted
	GrpIdBytes hexutil.Bytes
	protocol.StepMessage
	ContextID uint64
	StepID    uint8

	Enc          bool
	Sig          []byte
	OriginPeerId uint8  //peerIndex
	FromPeerId   uint8  //peerIndex
	KnownPeerIds uint64 // peerIndex || peerIndex
	Hash         common.Hash
	rwMutex      sync.RWMutex // convert to MpcMessage error.
}

func (mm *MpcMessage) AddKnowPeerIds(ids []*discover.NodeID) {
	defer mm.rwMutex.Unlock()
	mm.rwMutex.Lock()
	//mm.KnownPeerIds |= osmconf.GetOsmConf().BuildNodesBitsByNodes(ids)
	mm.KnownPeerIds |= osmconf.GetOsmConf().BuildNodesBitsByNodes(hexutil.Encode(mm.GrpIdBytes), ids)
}

func (mm *MpcMessage) GetKnowPeerIds() uint64 {
	defer mm.rwMutex.RUnlock()
	mm.rwMutex.RLock()
	return mm.KnownPeerIds //todo mutext
}

func (mm *MpcMessage) Clone() *MpcMessage {
	var ret MpcMessage
	peerId := *mm.PeerID
	originPeerId := mm.OriginPeerId

	ret.ContextID = mm.ContextID
	ret.StepID = mm.StepID

	ret.Enc = mm.Enc
	ret.Sig = mm.Sig

	ret.MsgCode = mm.MsgCode
	ret.PeerID = &peerId
	ret.OriginPeerId = originPeerId

	ret.Data = mm.Data
	ret.BytesData = mm.BytesData
	ret.StepId = mm.StepId
	ret.KnownPeerIds = mm.KnownPeerIds
	ret.Hash = mm.Hash
	ret.rwMutex = sync.RWMutex{}
	ret.GrpIdBytes = mm.GrpIdBytes
	return &ret
}

func (mm *MpcMessage) UpdateFromPeerID(newFrom *discover.NodeID) {

	newPeerId := *newFrom
	//mm.FromPeerId = osmconf.GetOsmConf().IndexByNodeId(newPeerId)
	mm.FromPeerId = osmconf.GetOsmConf().IndexByNodeId(hexutil.Encode(mm.GrpIdBytes), newPeerId)
}

func (mm *MpcMessage) MessageKey() (common.Hash, error) {
	return mm.Hash, nil
}

func (mm *MpcMessage) BuildHash() (common.Hash, error) {
	var buf bytes.Buffer
	buf.Write(big.NewInt(0).SetUint64(mm.ContextID + uint64(mm.StepID)).Bytes())
	for _, data := range mm.Data {
		buf.Write(data.Bytes())
	}
	for _, byteData := range mm.BytesData {
		buf.Write(byteData[:])
	}
	// peerID
	if mm.PeerID != nil {
		buf.Write(mm.PeerID.Bytes())
	}
	// Origin peerID
	if mm.OriginPeerId != 0 {
		buf.Write(big.NewInt(0).SetUint64(uint64(mm.OriginPeerId)).Bytes())
	}
	buf.Write(mm.GrpIdBytes[:])
	hash := sha3.Sum256(buf.Bytes())
	copy(mm.Hash[:], hash[:])
	return hash, nil
}

func (mm *MpcMessage) Encypt() error {
	selfId, err := osmconf.GetOsmConf().GetSelfNodeId()
	if err != nil {
		log.SyslogErr("MpcMessage Encypt", "error", err.Error())
		return err
	}
	// broadcast message and send self message need not encrypt
	if protocol.IsNilNodeId(mm.PeerID) || bytes.Equal((*mm.PeerID)[:], (*selfId)[:]) {
		mm.setEncyStatus(false)
		return nil
	}

	// get the receives' pk to encrypt data
	pk, err := osmconf.GetOsmConf().GetFirstPKByNodeId(mm.PeerID)
	if err != nil {
		log.SyslogErr("MpcMessage Encypt GetFirstPKByNodeId", "error", err.Error())
		return err
	}
	log.SyslogDebug("MpcMessage Encypt", "pk", hex.EncodeToString(crypto.FromECDSAPub(pk)), "msg", mm.String())
	// encrypt data
	for index, itemData := range mm.Data {
		itemDataEnc, err := schnorrcomm.EncData(pk, itemData.Bytes())
		if err != nil {
			log.SyslogErr("MpcMessage Encypt EncData", "error", err.Error())
			return err
		}
		log.SyslogDebug("MpcMessage Encypt EncData", "msg", mm.String(), "EncData", hex.EncodeToString(itemDataEnc))
		mm.Data[index] = *big.NewInt(0).SetBytes(itemDataEnc)
	}
	// encrypt bytedata
	for index, itemByte := range mm.BytesData {
		itemByteEnc, err := schnorrcomm.EncData(pk, itemByte[:])
		if err != nil {
			log.SyslogErr("MpcMessage Encypt EncData bytes", "error", err.Error())
			return err
		}
		mm.BytesData[index] = itemByteEnc
	}
	mm.setEncyStatus(true)
	return nil
}

func (mm *MpcMessage) DecryptData() error {

	if !mm.EncyStatus() {
		log.SyslogDebug("MpcMessage DecryptData: plain message no need decrypt")
		return nil
	}

	prv, err := osmconf.GetOsmConf().GetSelfPrvKey()
	if err != nil {
		log.SyslogErr("MpcMessage  DecryptData", "GetSelfPrvKey error", err.Error())
	}

	// decrypt data
	for index, itemData := range mm.Data {
		itemDataPlain, err := schnorrcomm.DecryptData(prv, itemData.Bytes())
		if err != nil {
			log.SyslogErr("MpcMessage DecryptData ", "error", err.Error(), "msg", mm.String(), "encyData", hex.EncodeToString(itemData.Bytes()))
			return err
		}
		mm.Data[index] = *big.NewInt(0).SetBytes(itemDataPlain)
	}
	// decrypt bytedata
	for index, itemByte := range mm.BytesData {
		itemBytePlain, err := schnorrcomm.DecryptData(prv, itemByte[:])
		if err != nil {
			log.SyslogErr("MpcMessage DecryptData  bytes", "error", err.Error(), "msg", mm.String())
			return err
		}
		mm.BytesData[index] = itemBytePlain
	}
	return nil
}

func (mm *MpcMessage) setEncyStatus(enc bool) {
	mm.Enc = enc
}

func (mm *MpcMessage) EncyStatus() bool {
	return mm.Enc
}

func (mm *MpcMessage) AddKeySig(prv *ecdsa.PrivateKey) error {
	hash, _ := mm.MessageKey()
	sig, err := schnorrcomm.Sign(hash[:], prv)
	if err != nil {
		return err
	}
	mm.Sig = sig
	log.SyslogDebug("AddKeySig", "hash", hex.EncodeToString(hash[:]), "sig", hex.EncodeToString(sig[:]), "mm.Sig", hex.EncodeToString(mm.Sig[:]))

	return nil
}

func (mm *MpcMessage) RecoveryPk(pub *ecdsa.PublicKey) ([]byte, error) {
	hash, _ := mm.MessageKey()
	return schnorrcomm.Ecrecover(hash[:], mm.Sig[:])
}

func (mm *MpcMessage) IsInGroup() bool {
	hash, _ := mm.MessageKey()
	hashBuild, err := mm.BuildHash()
	if !bytes.Equal(hash[:], hashBuild[:]) || err != nil {
		if err != nil {
			log.SyslogErr("IsInGroup", "err", fmt.Sprintf("BuildHash err :%v", err.Error()))
		} else {
			log.SyslogErr("IsInGroup", "hash is wrong", fmt.Sprintf("BuildHash :%v,hash:%v", hashBuild.Hex(), hash.Hex()))
		}
		return false
	}
	pk, err := schnorrcomm.SigToPub(hash[:], mm.Sig[:])
	if err != nil {
		log.SyslogErr("IsInGroup", "err", err.Error())
		return false
	}
	return osmconf.GetOsmConf().IsInGroup(pk)
}

func (mm *MpcMessage) IsMyMsg() (bool, bool) {
	// for all, including me
	if protocol.IsNilNodeId(mm.PeerID) {
		return true, false
	}
	// for me ,and only for me
	selfNodeId, _ := osmconf.GetOsmConf().GetSelfNodeId()
	if bytes.Equal((*selfNodeId)[:], (*mm.PeerID)[:]) {
		return true, true
	}
	// not for me,not for only me
	return false, false

}

func (mm *MpcMessage) IsValid() bool {
	if mm == nil || len(mm.Sig) == 0 || mm.ContextID == uint64(0) {
		log.SyslogErr("IsValid", "mm", mm, "len(mm.Sig)", len(mm.Sig), "mm.ContextID", mm.ContextID)
		return false
	}

	// not in group
	if !(mm.IsInGroup()) {
		log.SyslogErr("IsValid", "IsInGroup()", mm.IsInGroup(), "mpcMessage", mm.String())
		return false
	}
	return true
}

func Uint64ToString(input uint64) string {
	return fmt.Sprintf("%b", input)
}
func (mm *MpcMessage) String() string {
	hash, _ := mm.MessageKey()
	var knowPeerIdsStr string
	knowPeerIdsStr = Uint64ToString(mm.GetKnowPeerIds())
	selfNodeId, _ := osmconf.GetOsmConf().GetSelfNodeId()
	//flatIndex := osmconf.GetOsmConf().IndexByNodeId(*selfNodeId)
	grpIdSlimByte := mm.GrpIdBytes[len(mm.GrpIdBytes)-10:]
	grpIdStr := hexutil.Encode(mm.GrpIdBytes[:])
	grpIdSlimStr := hexutil.Encode(grpIdSlimByte[:])
	flatIndex := osmconf.GetOsmConf().IndexByNodeId(grpIdStr, *selfNodeId)
	return fmt.Sprintf("ContextID %d,StepId %d,Enc %t,MsgCode %s, PeerID %v, OriginPeerID %v, FromPeerID %v, selfFlatIndex %v,msgHash %v, knownList %v, grpIdStr %v\n",
		mm.ContextID,
		mm.StepID,
		mm.EncyStatus(),
		protocol.GetMsgCodeStr(mm.MsgCode),
		mm.PeerID.SlimString(),
		mm.OriginPeerId,
		mm.FromPeerId,
		flatIndex,
		fmt.Sprintf("0x%.4x", hash[:]),
		knowPeerIdsStr,
		grpIdSlimStr)
}
